import { computed, reactive, ref, watch } from 'vue';
import { isObject } from '@/utils/common';

/**
 * 将接口返回字段映射为组件字段
 */
const REFLECTION = {
  currentPage: 'number',
  pageSize: 'size',
  dataSource: 'content',
  result: 'result',
  total: 'total',
};

/**
 * 将组件字段反射为接口请求字段
 */
const REFLECT = {
  currentPage: 'pageNumber',
  pageSize: 'pageSize',
};

/**
 * @typedef {Object} UseVxePagerOption
 * @property {import("vxe-table").VxePagerProps} pagination - VxePager props
 * @property {object} reflection - 将接口返回字段映射为组件字段
 * @property {object} reflect - 将组件字段反射为接口请求字段
 * @property {() => Promise<{ code: string; msg: string; result: unknown }>} load - 数据加载
 * @property {Function} onLoadFail - 数据加载失败的回调函数
 * @property {Function} onLoadComplete - 数据加载成功的回调函数
 * @property {unknown} initial - 数据默认值
 * @property {unknown} watcher - 监听器。当监听到变化后将直接调用 reset 重置分页并加载数据
 * @property {import("vue").WatchOptions<boolean = false>} watchOption - watch 的 option
 */

/**
 * @typedef {Object} UseVxePagerReturn
 * @property {import("vxe-table").VxePagerProps} config - VxePager props
 * @property {unknown} data - 表格数据
 * @property {(page: { currentPage?: number, pageSize?: number }) => void} change - 数据加载失败的回调函数
 * @property {(initial?: boolean) => void} reset - 数据加载成功的回调函数
 */

/**
 * @param {UseVxePagerOption} useVxePagerOption
 * @returns {UseVxePagerReturn}
 */
export function useVxePager({
  pagination: _pagination,
  reflection,
  reflect,
  load: loadFn,
  onLoadComplete,
  onLoadFail,
  initial,
  immediate,
  watcher,
  watchOption,
}) {
  function _initial() {
    return typeof initial === 'function' ? initial() : initial;
  }

  const data = ref(_initial());

  reflection = reflection ? { ...REFLECTION, ...reflection } : REFLECTION;
  reflect = reflect ? { ...REFLECT, ...reflect } : REFLECT;

  const pagination = reactive({
    pageSizes: [10, 20, 50, 100, 200, 300, 400],
    currentPage: 1,
    pageSize: 20,
    loading: false,
    layouts: ['Total', 'Sizes', 'PrevPage', 'Number', 'NextPage', 'FullJump'],
    ...(_pagination ?? {}),
    style: {
      marginTop: '12px',
      // 模拟 v-show，兼容 vxe-pager
      display: computed(() => (data.value?.length ? undefined : 'none')),
    },
    // 兼容 vxe-grid pager-config
    enabled: computed(() => !!data.value?.length),
  });

  let pageCache = null;

  function change(event) {
    pageCache = assemblePagination(event);
    Object.assign(pagination);

    load();
  }

  async function load() {
    const { pageSize, currentPage } = pageCache ?? pagination;

    const page = {
      [reflect.pageSize]: pageSize,
      [reflect.currentPage]: currentPage,
    };

    try {
      pageCache = null;
      pagination.loading = true;

      const { code, msg, [reflection.result]: result } = await loadFn(page);

      if (code === '200') {
        if (isObject(result)) {
          data.value = result[reflection.dataSource];

          Object.assign(pagination, {
            total: result[reflection.total] ?? data.value.length,
            pageSize: result[reflection.pageSize],
            currentPage: result[reflection.currentPage],
          });

          onLoadComplete?.(result);
        } else {
          throw new Error(`axios response.${reflection.result} is not an object`);
        }
      } else {
        throw new Error(msg);
      }
    } catch (error) {
      Object.assign(pagination, { ...page, total: data.value?.length || 0 });
      data.value = _initial();
      onLoadFail?.(error);

      if (import.meta.env.DEV) {
        throw error;
      }
    } finally {
      pagination.loading = false;
    }
  }

  function assemblePagination(data) {
    let { currentPage, pageSize } = data ?? {};
    currentPage ||= pagination.currentPage;
    pageSize ||= pagination.pageSize;

    return { pageSize, currentPage };
  }

  /**
   *
   * @param {boolean} initial 使用 initial 数据进行重置。为 false 时将使用 onLoad 重载数据。
   */
  function reset(initial) {
    if (initial === true) {
      data.value = _initial();
      Object.assign(pagination, { currentPage: 1, total: data.value?.length || 0 });
    } else {
      change({ currentPage: 1 });
    }
  }

  immediate = watcher ? (watchOption?.immediate ?? immediate) : immediate;

  const stop = watcher
    ? watch(watcher, () => reset(), watchOption ? { immediate, ...watchOption } : { immediate })
    : (immediate && reset(), () => {});

  const reload = load;

  return {
    data,
    pagination,
    change,
    reset,
    reload,
    stop,
    *[Symbol.iterator]() {
      yield data;
      yield pagination;
      yield change;
      yield reset;
      yield reload;
      yield stop;
    },
  };
}
